V10.2.1/normalized semanitc versions - #36
Conversation
Add version normalization to RestfulApiVersionReader to treat semantically equivalent versions (1, 1.0, 1.0.0) as identical for routing purposes. Introduces PreviousBehavior compatibility flag to preserve existing behavior when needed. Routes matching on both Accept and Content-Type headers now correctly normalize versions before comparison.
Add comprehensive functional tests for semantic version normalization behavior. Tests verify that Accept and Content-Type header versions with equivalent semantic values (e.g., 1, 1.0, 1.0.0) are correctly normalized and routed to the same endpoint. Includes scenarios for matching versions, mismatched versions, and unregistered version aliases.
Update namespace overview and type documentation for Codebelt.Extensions.Asp.Versioning to reflect version normalization feature. Add new type documentation for ApiVersionAliasParser. Update ServiceCollectionExtensions documentation to include examples and guidance for configuring version normalization behavior.
Update AGENTS.md to reflect the version normalization feature and its behavioral implications for API routing and version matching.
Greptile SummaryThis PR introduces v10.2.1 of
Confidence Score: 5/5Safe to merge; the normalization logic is sound and well-covered by the new functional tests. The core normalization algorithm correctly handles all observable cases: equivalent aliases deduplicate to a canonical form, genuinely different versions pass through unchanged and are rejected by the framework as ambiguous, and parse failures fall back to original behavior. The PreviousBehavior flag ensures non-semantic defaults are unaffected. Test coverage is thorough across 9 alias-equivalence combinations plus edge cases for different versions and unregistered aliases. No files require special attention. RestfulApiVersionReader.cs and ServiceCollectionExtensions.cs are the core changed files and both read cleanly. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant C as Client
participant R as RestfulApiVersionReader
participant B as MediaTypeApiVersionReader (base)
participant P as IApiVersionParser
participant F as Asp.Versioning Framework
C->>R: "HTTP POST (Accept: v=1, Content-Type: v=1.0)"
R->>B: base.Read(request)
B-->>R: ["1", "1.0"]
Note over R: PreviousBehavior=false AND Count>1
R->>P: "GetService<IApiVersionParser>()"
P-->>R: parser (alias-aware)
R->>P: TryParse("1")
P-->>R: SemanticApiVersion(1,0,0)
R->>P: TryParse("1.0")
P-->>R: SemanticApiVersion(1,0,0)
Note over R: Deduplicated to ["1.0.0"]
R-->>F: ["1.0.0"]
F-->>C: 204 NoContent (routed to v1 endpoint)
Note over C,F: Non-semantic default (PreviousBehavior=true)
C->>R: "HTTP POST (Accept: v=1, Content-Type: v=1.0.0)"
R->>B: base.Read(request)
B-->>R: ["1", "1.0.0"]
Note over R: PreviousBehavior=true, early return
R-->>F: ["1", "1.0.0"]
F-->>C: 400 BadRequest (ambiguous versions)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant C as Client
participant R as RestfulApiVersionReader
participant B as MediaTypeApiVersionReader (base)
participant P as IApiVersionParser
participant F as Asp.Versioning Framework
C->>R: "HTTP POST (Accept: v=1, Content-Type: v=1.0)"
R->>B: base.Read(request)
B-->>R: ["1", "1.0"]
Note over R: PreviousBehavior=false AND Count>1
R->>P: "GetService<IApiVersionParser>()"
P-->>R: parser (alias-aware)
R->>P: TryParse("1")
P-->>R: SemanticApiVersion(1,0,0)
R->>P: TryParse("1.0")
P-->>R: SemanticApiVersion(1,0,0)
Note over R: Deduplicated to ["1.0.0"]
R-->>F: ["1.0.0"]
F-->>C: 204 NoContent (routed to v1 endpoint)
Note over C,F: Non-semantic default (PreviousBehavior=true)
C->>R: "HTTP POST (Accept: v=1, Content-Type: v=1.0.0)"
R->>B: base.Read(request)
B-->>R: ["1", "1.0.0"]
Note over R: PreviousBehavior=true, early return
R-->>F: ["1", "1.0.0"]
F-->>C: 400 BadRequest (ambiguous versions)
Reviews (2): Last reviewed commit: "♻️ update api version reader type and im..." | Re-trigger Greptile |
| /// <value>The valid accept headers that <see cref="ReadAcceptHeader"/> will filter by.</value> | ||
| public IList<string> ValidAcceptHeaders { get; } | ||
|
|
||
| internal bool PreviousBehavior { get; set; } |
There was a problem hiding this comment.
PreviousBehavior is internal but release notes describe it as a user-facing opt-out
The PackageReleaseNotes.txt and CHANGELOG.md both state "a PreviousBehavior compatibility flag available for opting out of the normalization," implying consumers can set it. Because the property has internal visibility, code outside the assembly cannot access it, so there is no public way to disable normalization on a user-constructed RestfulApiVersionReader. Additionally, since PreviousBehavior defaults to false, any RestfulApiVersionReader constructed by a caller and supplied via RestfulApiVersioningOptions.ApiVersionReader will silently have normalization enabled with no escape hatch.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/Codebelt.Extensions.Asp.Versioning/RestfulApiVersionReader.cs
Line: 33
Comment:
`PreviousBehavior` is `internal` but release notes describe it as a user-facing opt-out
The `PackageReleaseNotes.txt` and `CHANGELOG.md` both state "a `PreviousBehavior` compatibility flag available for opting out of the normalization," implying consumers can set it. Because the property has `internal` visibility, code outside the assembly cannot access it, so there is no public way to disable normalization on a user-constructed `RestfulApiVersionReader`. Additionally, since `PreviousBehavior` defaults to `false`, any `RestfulApiVersionReader` constructed by a caller and supplied via `RestfulApiVersioningOptions.ApiVersionReader` will silently have normalization enabled with no escape hatch.
How can I resolve this? If you propose a fix, please make it concise.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #36 +/- ##
==========================================
- Coverage 99.45% 98.97% -0.49%
==========================================
Files 11 11
Lines 553 584 +31
Branches 86 93 +7
==========================================
+ Hits 550 578 +28
- Misses 3 6 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This pull request introduces a minor release (v10.2.1) of the
Codebelt.Extensions.Asp.Versioninglibrary, focused on improving API version normalization and routing consistency. The main enhancement ensures that semantically equivalent API version formats (such as1,1.0, and1.0.0) are normalized to a canonical form, leading to more robust and predictable version matching. Additional customization options and new tests are included to support and validate this behavior.Key improvements and fixes:
API Version Normalization and Routing Consistency
RestfulApiVersionReaderto normalize semantically equivalent API version strings to a canonical format, ensuring consistent routing and version matching. This normalization is enabled by default when the default API version is aSemanticApiVersion, but can be opted out of via a compatibility flag (PreviousBehavior). [1] [2]RestfulApiVersioningOptionsto include a customizableApiVersionReaderproperty, allowing advanced customization of how API versions are read from requests. [1] [2]AddRestfulApiVersioningto automatically enable version normalization and register semantic version aliases when aSemanticApiVersionis the default.Documentation and Release Notes
AddApiVersionParser, and theApiVersionAliasParserfor handling short version tokens. [1] [2] [3] [4]Testing Enhancements
Project Structure